Repository navigation
fix(controller): take Local targets from the nodes kube-proxy serves - #56
Merged
Merged
Conversation
A Local service with a selector took its targets from the pods the selector matched. That kept nodes where kube-proxy had no endpoint to send traffic to, such as the node of a pod that is not ready yet, and so nodes that fail the balancer's health check. A cordoned or not-ready node running a matching pod stayed a target too, unlike under the Cluster policy, with nothing saying whether that was intended. Every Local service now takes its targets from its EndpointSlices, the path already used for services without a selector. A node is a target when kube-proxy serves Local traffic from it: it has a ready endpoint, or a terminating endpoint that still serves, which kube-proxy falls back to while the node has no ready one. Missing conditions are read the way kube-proxy reads them. Targets and the slice trigger share this filter, so the trigger no longer needs a separate pod-based mode. robotlb no longer reads pods at all, and the chart's default role drops its access to them. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la>
s3rius
approved these changes
Oct 6, 2026
s3rius
pushed a commit
that referenced
this pull request
Oct 13, 2026
…ng (#49) * perf(controller): react to node and endpoint changes instead of polling Every LoadBalancer service was reconciled every 30 seconds, and each run cost one or two Hetzner API requests even when nothing changed. The short interval was there because only Services were watched, so a new node or a moved pod reached the balancer only through that requeue. Node changes that affect targets now reconcile every service. Heartbeat updates are ignored, since only labels, cordoning, readiness and addresses are compared. An endpoint slice change reconciles its service when it is a robotlb balancer with the Local traffic policy and the nodes its targets follow changed: all endpoint nodes when targets come from pods, ready endpoint nodes otherwise. Readiness flaps and address changes that leave those nodes alone cost nothing. The controller's watch mapper cannot tell a deletion from an update, so a second watch tracks which slices exist; a deleted slice with endpoints reconciles every service. The periodic resync is now ROBOTLB_RESYNC_INTERVAL, five minutes by default and at most a year. A reconcile that could not add some targets, for a reason other than the rate limit, is retried after 30 seconds like a failed one, so a temporarily rejected node does not wait for the resync. Pods that have finished or have no IP yet no longer count as targets. Neither is in the endpoint slice, so deleting them would leave their node a target until the resync. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la> * fix(controller): take Local targets from the nodes kube-proxy serves (#56) A Local service with a selector took its targets from the pods the selector matched. That kept nodes where kube-proxy had no endpoint to send traffic to, such as the node of a pod that is not ready yet, and so nodes that fail the balancer's health check. A cordoned or not-ready node running a matching pod stayed a target too, unlike under the Cluster policy, with nothing saying whether that was intended. Every Local service now takes its targets from its EndpointSlices, the path already used for services without a selector. A node is a target when kube-proxy serves Local traffic from it: it has a ready endpoint, or a terminating endpoint that still serves, which kube-proxy falls back to while the node has no ready one. Missing conditions are read the way kube-proxy reads them. Targets and the slice trigger share this filter, so the trigger no longer needs a separate pod-based mode. robotlb no longer reads pods at all, and the chart's default role drops its access to them. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la> --------- Signed-off-by: Aleksei Sviridkin <f@lex.la>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Local services now take their targets from their EndpointSlices, the same way services without a selector already did. Before, a service with a selector used the nodes of the pods it matched. So a node with a pod that was not ready yet stayed a target while kube-proxy had nothing there to send traffic to. A cordoned or not-ready node running a pod always stayed a target too, and now its endpoints decide.
A node is a target when kube-proxy serves Local traffic from it. That means a ready endpoint on the node, or a terminating endpoint that still serves, which kube-proxy falls back to while the node has no ready one. Missing conditions are read the way kube-proxy reads them: ready and serving default to true, terminating to false. See topology.go and endpointslicecache.go.
The endpoint slice trigger from #49 uses the same filter now. Its pod-based mode is gone, and robotlb does not read pods anymore, so the chart's default role no longer grants access to them. A custom
serviceAccount.permissionslist is not touched.Stacked on #49.
Closes #48